/**
 *
 */
package com.seteam.collabcrossword;

import com.seteam.collabcrossword.exceptions.CollabCrosswordException;
import com.seteam.collabcrossword.exceptions.CoordinatesException;
import com.seteam.collabcrossword.messages.Message;
import com.seteam.collabcrossword.messages.server.CannotLock;
import com.seteam.collabcrossword.messages.server.CrosswordAndClues;
import com.seteam.collabcrossword.messages.server.LockedWord;
import com.seteam.collabcrossword.types.Direction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

/**
 * This class contains all the Shared resources between the two clients who are
 * solving the crossword together.
 * 
 * @author SE Team
 */
public class Resources {
	private final Logger logger = LoggerFactory.getLogger(MessageParser.class);

	private CrosswordAndClues crosswordAndClues;
	private CrosswordSolution crosswordSolution;
	private CrosswordCurrentState crosswordCurrent;
	// Barrier used to create the clause of
	// "two should join for the game to begin"
	private CyclicBarrier barrier;

	/**
	 * Constructor. Creates the three shared resources between the 2 clients.
	 * This includes CrossWord Current state, Crossword Solution and the Initial
	 * crossword along with the clues.
	 * 
	 * @throws Exception
	 */
	public Resources() throws Exception {
		crosswordAndClues = new CrosswordAndClues();
		// Method that creates all the instance of the shared resources.
		init();
		// Barrier is created with the value 2.
		barrier = new CyclicBarrier(2);
	}

	/**
	 * The initialisation method for the shared resources between the two
	 * clients.
	 * 
	 * @throws Exception
	 */
	private synchronized void init() throws Exception {
		CrosswordGenerator crosswordGenerator = new CrosswordGenerator(13, 13);
		// Generate the crossword
		crosswordGenerator.generateCrossword();
		// get the crossword
		this.crosswordAndClues.setCrossword(crosswordGenerator.getCrossword());
		this.crosswordAndClues.setAcross(crosswordGenerator.getCluesAcross());
		this.crosswordAndClues.setDown(crosswordGenerator.getCluesDown());

		// get the solutions
		this.crosswordSolution = crosswordGenerator.getSolution();

		// get the currentstate
		this.crosswordCurrent = crosswordGenerator.getCrosswordCurrentState();
	}

	/**
	 * Getter method for the initial Crossword with Clues.
	 * 
	 * @return CrosswordAndClues objects that contains all the details of the
	 *         Crossword along with the Clues associated with each word of the
	 *         crossword.
	 */
	public CrosswordAndClues getCrossword() {
		try {

			logger.info("Waiting at the barrier {}", this.hashCode());
			// The threads wait here until two threads
			// join the game.
			barrier.await();
		} catch (InterruptedException e) {
			logger.error("Exception: {}", e.getMessage());
		} catch (BrokenBarrierException e) {
			logger.error("Exception: {}", e.getMessage());
		}
		logger.info("Thread {} going through", Thread.currentThread().getId());
		return crosswordAndClues;
	}

	/**
	 * 
	 * @return
	 */
	public synchronized CrosswordSolution getCrosswordSolution() {
		return crosswordSolution;
	}

	/**
	 * Getter method to get the current state of the crossword.
	 * 
	 * @return CrosswordCurrentState object which sends a copy of the current
	 *         state of the crossword maintained in the server.
	 */
	public synchronized CrosswordCurrentState getCrosswordCurrent() {
		return crosswordCurrent;
	}

	/**
	 * This method updates each letter updated by the client in the server's
	 * current state.
	 * 
	 * @param tile
	 */
	public synchronized void updateLetter(Tile tile) {

		// Get the Tile from the UI, which is a representation of the actual
		// tile.
		// It contains only the co-ordinate and the character to be updated.
		Coordinates coordinate = tile.getCoordinates();
		Tile toUpdateTile = null;
		try {
			// Get the tile from the current state
			toUpdateTile = crosswordCurrent.getTile(coordinate);
			toUpdateTile.setLetter(tile.getLetter());
		} catch (Exception e) {
			logger.error("Exception {}", e.getMessage());
		}

		// Update the letter in the current crossword state.
		crosswordCurrent.updateLetter(toUpdateTile);

		logger.info("Got the letter," + tile.getLetter() + " and updated it.");

	}

	/**
	 * This method is called when a user wants to lock a word so that he can
	 * edit it. Here we go through the current state of the crossowrd maintained
	 * by the server, and if the word requested by the client is available(not
	 * locked) then locks the word for the client. Else sends a message that the
	 * lock cannot be acquired.
	 * 
	 * @param startOfWord
	 *            The coordinates of the start of the word
	 * @param direction
	 *            The direction in which the lock has to be acquired.
	 * @return Message telling if the lock was granted or not.
	 */
	public synchronized Message lockWord(final Coordinates startOfWord,
			final Direction direction) {
		List<Coordinates> lockedCoordinates = new ArrayList<Coordinates>();
		int row = startOfWord.getxCoordinate();
		int column = startOfWord.getyCoordinate();
		final Tile[][] tileArray = crosswordCurrent.getCurrentState();
		Tile current = null;
		boolean canLock = true;
		if (direction == Direction.ACROSS) {
			canLock = true;
			while (!tileArray[row][column].isBlocked()) {
				current = tileArray[row][column];
				try {
					if (!current.isLockedAcross()) {
						current.lockAcross(true);
						tileArray[row][column] = current;
						lockedCoordinates.add(new Coordinates(row, column));
					} else {
						canLock = false;
						lockedCoordinates.clear();
						break;
					}
				} catch (final CollabCrosswordException e) {
					logger.error("Exception: {}", e.getMessage());
				}
				column++;
                if(column > tileArray[row].length - 1) {
                    break;
                }
			}
		}

		if (direction == Direction.DOWN) {
			canLock = true;
			while (!tileArray[row][column].isBlocked()) {
				current = tileArray[row][column];
				try {
					if (!current.isLockedDown()) {
						current.lockDown(true);
						tileArray[row][column] = current;
						lockedCoordinates.add(new Coordinates(row, column));
					} else {
						canLock = false;
						lockedCoordinates.clear();
						break;
					}
				} catch (final CollabCrosswordException e) {
					logger.error("Exception: {}", e.getMessage());
				}
				row++;
                if(row == tileArray.length) {
                    break;
                }
			}
		}

		if (lockedCoordinates.isEmpty()) {
			logger.info("Could not lock word starting at {} {}", row, column);
			logger.info("Lock requested for word going", direction);
			return new CannotLock();
		} else {
			crosswordCurrent.setCurrentState(tileArray);
			logger.info("Locked word starting at {} {}", row, column);
			logger.info("Lock requested for word going", direction);
			final LockedWord lockedWord = new LockedWord();
			for (final Coordinates coordinates : lockedCoordinates) {
				try {
					lockedWord.addCoordinates(coordinates);
				} catch (final CoordinatesException e) {
					logger.error("Exception: {}", e.getMessage());
				}
			}
			return lockedWord;
		}
	}

	/**
	 * Releases the locks on all the words. This method is requested when one
	 * client quits, which implies the other client can get the locks to any
	 * word on the board, so all the locks are released.
	 */
	public synchronized void releaseLocks() {
		logger.info("Releasing Locks");
		Tile[][] tileArray = crosswordCurrent.getCurrentState();
		int row = crosswordCurrent.getDimX();
		int column = crosswordCurrent.getDimY();
		for (int i = 0; i < row; i++) {
			for (int j = 0; j < column; j++) {
				if (!tileArray[i][j].isBlocked()) {
					// TODO find if setLock is required
					tileArray[i][j].setLock(false);
					// Remove the across and down locks on all the tiles.
					tileArray[i][j].lockDown(false);
					tileArray[i][j].lockAcross(false);
				}
			}
		}

		crosswordCurrent.setBoard(tileArray);

	}
}
